package pers.chao.cacheable.core;

import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import pers.chao.cacheable.core.annotation.Cache;
import pers.chao.cacheable.core.consts.CacheExpireUnit;
import pers.chao.cacheable.core.consts.CacheStrategy;
import pers.chao.cacheable.infrastructure.ErrorDesc;
import pers.chao.cacheable.infrastructure.utils.RedisOp;
import pers.chao.tool.infrastructure.log.BaseLogFactory;
import pers.chao.tool.infrastructure.regex.RegexPattern;
import pers.chao.tool.spring.SpringElUtil;
import pers.chao.tool.validate.ObjValidator;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * Description: redis缓存基础处理类
 *
 * @author W.Chao
 * @date 2020/10/2 0:58
 **/
public abstract class AbstractCacheProcessor {
	protected String prefix;
	protected String key; // 可能带有占位符的key
	protected CacheStrategy strategy; // 缓存策略
	protected CacheExpireUnit unit; // 过期时间单位
	protected long expire; // 过期时间
	protected boolean isNeedExpire; // 是否设置过期
	protected boolean evict; // 清除缓存标识
	protected boolean update; // 更新缓存标识
	protected String separator; // key分隔符

	protected Map<String, Object> params; // 方法参数
	protected String finalKey;
	protected boolean hasParam;
	protected Method method;

	// *****only used design ideas before********
	private static final String KEY_SEPARATOR = "::"; // prefix和finalKey的间隔符，已支持多种分隔符
	private static final String KEY_LINK_SYMBOL = "\\+"; // 多key连接的连接符号，下面分隔时使用变量会有歧义问题，导致if判断失败，改为硬编码
	protected static final String PLACEHOLDER = "#"; // 占位符号，已使用springEL表达式解析
	// ******************************************

	protected static final Logger logger = BaseLogFactory.getLogger(AbstractCacheProcessor.class);

	/**
	 * 方法执行前的预处理
	 *
	 * @param cache
	 * @param params
	 * @return
	 */
	public Object preProcess(Cache cache, Map<String, Object> params, Method method) {
		this.prefix = cache.prefix();
		this.key = cache.key();
		this.strategy = cache.strategy();
		this.unit = cache.unit();
		this.evict = cache.evict();
		this.update = cache.update();
		this.params = params;
		this.finalKey = "";
		this.hasParam = params != null && params.size() > 0;
		this.method = method;
		this.separator = cache.separator().getValue();
		this.expire = RegexPattern.isNumeric(cache.expire()) ? Long.valueOf(cache.expire()) : Long.valueOf(objectKeyParse(cache.expire(), "0"));
		this.isNeedExpire = this.expire != 0L;

		// key解析
		parseKey();
		// 具体数据结构的自定义预处理
		parseRedisStructKeyOrOther();
		// 信息预处理，子类自定义实现
		preInfo();
		// 预查询
		return preQuery();
	}

	/**
	 * 预处理信息处理，子类自行实现
	 */
	protected abstract void preInfo();

	/**
	 * finalKey获取
	 */
	private void parseKey() {
		this.key = objectKeyParse(this.key, this.key);
		/**
		 * 多个key连接情况枚举:
		 * 1. prefix::key1
		 * 2. prefix::key1::key2
		 * 3. key
		 * 4. key1::key2
		 * 5. string1::key1    -->   普通string和对象字段组合
		 */
		this.finalKey = ObjValidator.isNotEmptyString(this.prefix) ? this.prefix + separator + this.key : this.key;
	}

	/**
	 * key转换，子类解析key也在用
	 * <p>
	 * 多值组合举例:
	 * #{p0}+#{p1}
	 *
	 * @param key see {@link #key} {@link Cache#key()}
	 * @return
	 */
	protected String objectKeyParse(String key, String defaultKey) {
		StringBuilder sb = new StringBuilder();
		Object[] argValues = params.values().toArray();
		if (key.contains("+")) {
			String[] keys = key.split("\\+");
			int length = keys.length;

			if (StrUtil.count(key, "+") != length - 1) {
				throw new CacheableException(ErrorDesc.PARAMETERS_COUNT_ERROR, "+");
			}

			for (int i = 0; i < length; i++) {
				String tmp = SpringElUtil.parseSpringEL(method, argValues, keys[i], String.class, keys[i]);
				if (i == length - 1) {
					sb.append(tmp);
				} else {
					sb.append(tmp).append(separator);
				}
			}
			return sb.toString();
		} else {
			return SpringElUtil.parseSpringEL(method, argValues, key, String.class, defaultKey);
		}
	}

	/**
	 * 具体redis结构的解析操作或其他操作
	 */
	public abstract void parseRedisStructKeyOrOther();

	/**
	 * 预查询，如果缓存中有则直接返回
	 * 该方法中，evict为true时，则在此步将键值删除
	 *
	 * @return
	 */
	public abstract Object preQuery();

	/**
	 * 不缓存 || (有则不缓存策略 && 存在该key) 时不缓存
	 * 更新模式则忽略缓存执行后续方法
	 *
	 * @return
	 */
	public boolean needCaching() {
		return !CacheStrategy.NC.equals(this.strategy)
				&& (!CacheStrategy.ENC.equals(this.strategy) || !RedisOp.hasKey(this.finalKey));
		// 等同如下
//		if (CacheStrategy.NC.equals(this.strategy)
//				|| (CacheStrategy.ENC.equals(this.strategy) && RedisOp.hasKey(this.finalKey))) {
//			return false;
//		}
//		return true;
	}

	/**
	 * 调用实际执行接口
	 *
	 * @param o method's result
	 */
	public void process(Object o) {
		afterMethodOperate(o);
	}

	/**
	 * redis操作，不同redis数据结构操作不同
	 *
	 * @param o
	 */
	public abstract void afterMethodOperate(Object o);

	/**
	 * 缓存未命中或缓存策略不执行，
	 * 子类自行覆盖
	 */
	public void finish() {
		logger.info("Cache not Hit OR evict OR update OR not query cache because of Cache strategy, information: {}", cacheConditionInfo());
	}

	/**
	 * 打印缓存的相关信息
	 *
	 * @return
	 */
	protected String cacheConditionInfo() {
		String expireMeaning = " (0L meaning no expire)";
		return "{prefix: " + this.prefix +
				", key(annotation param): " + this.key +
				", strategy: " + this.strategy.getStrategy() +
				", unit: " + this.unit.getUnit() +
				", expire: " + this.expire + expireMeaning +
				", evict: " + this.evict +
				", update mode: " + this.update +
				", separator: " + this.separator +
				", parameters: " + this.params +
				", final key(parsed): " + this.finalKey +
				"}";
	}

	public String getPrefix() {
		return prefix;
	}

	public CacheStrategy getStrategy() {
		return strategy;
	}

	public CacheExpireUnit getUnit() {
		return unit;
	}

	public long getExpire() {
		return expire;
	}

	public boolean isEvict() {
		return evict;
	}

	public String getFinalKey() {
		return finalKey;
	}

	public boolean isNeedExpire() {
		return isNeedExpire;
	}

	public boolean isUpdate() {
		return update;
	}
}
